﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BBCommon;
using System.Drawing;
using Lidgren.Library.Network;

using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Business
{
   public class ChatApplication
   {
      #region Constructeur

      public ChatApplication()
      {
         this.PList = new PlayerList();
         this.Config = new NetAppConfiguration("BBTcpTest");

         this.Config.EnableEncryption(
              "AQABwV1rQ1NDWzkkhUsYtMiRGjyxSn/zI13tros2RlXHlW6PA7Cvw2nOyMhDmweM+" +
              "T/+FWzssAWLh8qd+YHaMCCFskVdaZfRzquZlNOs9mX/vtxkLrXgBUaJQD/XOuBeJF" +
              "o3RfAKA4uhrEr7Bk1iB3zJQW1bSHK7KmmEWzMErk6iA7c=", null);

         this.Log = new NetLog();
         Log.IgnoreTypes = NetLogEntryTypes.Verbose; //  Verbose;
         Log.IsOutputToFileEnabled = false;

         Log.OutputFileName = "clientlog.html";

         this.Client = new NetClient(this.Config, this.Log);
      }

      #endregion

      #region Properties

      public NetClient Client;
      public NetLog Log;
      public NetAppConfiguration Config;
      public PlayerList PList;
      public int OurID;

      #endregion

      #region Events

      public delegate void SystemMessage(string message, BBMessage BBmessage);
      /// <summary>
      /// System message
      /// </summary>
      public event SystemMessage SendingSystemMessage;
      private void OnSendingSystemMessage(string message, BBMessage BBmessage)
      {
         if (this.SendingSystemMessage != null)
            this.SendingSystemMessage(message, BBmessage);
      }

      public delegate void UserConnectionEvent(PlayerCarac playerCarac, bool newPlayer);
      /// <summary>
      /// User Connection
      /// </summary>
      public event UserConnectionEvent UserConnection;
      private void OnUserConnection(PlayerCarac player, bool newPlayer)
      {
         if (this.UserConnection != null)
            this.UserConnection(player, newPlayer);
      }

      public delegate void UserDisconnectionEvent(PlayerCarac playerCarac, string reason);
      /// <summary>
      /// User disconnection
      /// </summary>
      public event UserDisconnectionEvent UserDisconnection;
      private void OnUserDisconnection(PlayerCarac player, string reason)
      {
         if (this.UserDisconnection != null)
            this.UserDisconnection(player, reason);
      }

      public delegate void TypingInformation(PlayerCarac playerCarac, BBConversation conversation, bool isTyping);
      /// <summary>
      /// When a user is typing a text
      /// </summary>
      public event TypingInformation UserTyping;
      private void OnUserTyping(PlayerCarac player, BBConversation conversation, bool isTyping)
      {
         if (this.UserTyping != null)
            this.UserTyping(player, conversation, isTyping);
      }

      /// <summary>
      /// When a user is knocked
      /// </summary>
      public event EventHandler UserKnocked;
      private void OnUserKnocked()
      {
         if (this.UserKnocked != null)
            this.UserKnocked(this, EventArgs.Empty);
      }

      /// <summary>
      /// When a user is inviting to eat
      /// </summary>
      public event EventHandler UserEat;
      private void OnUserEat()
      {
         if (this.UserEat != null)
            this.UserEat(this, EventArgs.Empty);
      }

      /// <summary>
      /// When a user is inviting to coffee
      /// </summary>
      public event EventHandler UserCoffee;
      private void OnUserCoffee()
      {
         if (this.UserCoffee != null)
            this.UserCoffee(this, EventArgs.Empty);
      }

      public delegate void SendingMessageEvent(BBMessage message);
      /// <summary>
      /// Sending a message
      /// </summary>
      public event SendingMessageEvent SendingMessage;
      private void OnSendingMessage(BBMessage message)
      {
         if (this.SendingMessage != null)
            this.SendingMessage(message);
      }

      /// <summary>
      /// Notify a message
      /// </summary>
      public event EventHandler NotifyMessage;
      public void OnNotifyMessage()
      {
         if (this.NotifyMessage != null)
            this.NotifyMessage(this, EventArgs.Empty);
      }

      /// <summary>
      /// VDM Message Activated
      /// </summary>
      public event EventHandler VDMActivated;
      public void OnVDMActivated()
      {
         if (this.VDMActivated != null)
            this.VDMActivated(this, EventArgs.Empty);
      }

      /// <summary>
      /// Bash Message activated
      /// </summary>
      public event EventHandler BASHActivated;
      public void OnBASHActivated()
      {
         if (this.BASHActivated != null)
            this.BASHActivated(this, EventArgs.Empty);
      }

      /// <summary>
      /// New pendu game
      /// </summary>
      public event EventHandler StartPenduNewWord;
      public void OnStartPenduNewWord()
      {
         if (this.StartPenduNewWord != null)
            this.StartPenduNewWord(this, EventArgs.Empty);
      }

      #endregion

      #region Methods

      /// <summary>
      /// Connection au serveur
      /// </summary>
      /// <param name="host"></param>
      /// <param name="port"></param>
      public void Connect(string host, int port)
      {
         this.Client.Connect(host, port);
      }

      public void HandleMessage(NetMessage netmsg)
      {
         BBMessage msg;

         // We received a message 
         Log.Debug("msg: " + netmsg);

         try
         {
            msg = BBMessage.FromNetMessage(netmsg);
         }
         catch (Exception ex)
         {
            Log.Error("Deserialization error : " + ex.Message);
            return;
         }

         Log.Debug("msg: " + System.Enum.GetName(typeof(BBMsgType), msg.MsgType));

         switch (msg.MsgType)
         {
            case BBMsgType.NO_LOGIN:
               OutputSystemText("Login refused ! Reason : " + msg.Param1, msg);
               break;

            case BBMsgType.OK_LOGIN:
               OutputSystemText("Login accepted ! ID is : " + msg.PlayerCarac.ActorID, msg);
               OurID = msg.PlayerCarac.ActorID;
               PList.AddPlayer(msg.PlayerCarac, null, null);
               this.OnUserConnection(msg.PlayerCarac, false);
               break;

            case BBMsgType.VDM:
               this.MessageChat(msg, false);
               if ((!string.IsNullOrEmpty(msg.Message)) && (msg.Param2))
                  this.OnVDMActivated();
               break;
            case BBMsgType.BASH:
               this.MessageChat(msg, false);
               if (!string.IsNullOrEmpty(msg.Message))
                  this.OnBASHActivated();
               break;
            case BBMsgType.PENDU:
               this.MessageChat(msg, false);
               if (msg.Param2)
                  this.OnStartPenduNewWord();
               break;
            case BBMsgType.CHAT:
               this.MessageChat(msg, true);
               break;
            case BBMsgType.TYPING_MSG:
               this.OnUserTyping(PList.GetPlayer(msg.ActorInfo.Id).Carac, msg.Conversation, msg.Param2);
               break;

            case BBMsgType.ADD_PLAYER:
               // Add the player to our player list
               PList.AddPlayer(msg.PlayerCarac, msg.ActorInfo, null);
               this.OnUserConnection(msg.PlayerCarac, msg.Param2);
               //MainForm.UserConnect(msg.PlayerCarac.Nick, msg.Param2);
               if (msg.Param2 == true)
               {
                  Log.Info("Player " + msg.PlayerCarac.Nick + " connected.");
               }
               break;
            case BBMsgType.DEL_PLAYER:
               Log.Info("Player " + PList.GetPlayer(msg.PlayerCarac.ActorID).Carac.Nick + " disconnected. Reason : " + msg.Param1);
               //MainForm.UserDisconnect(PList.GetPlayer(msg.PlayerCarac.ActorID).Carac.Nick, msg.Param1);
               this.OnUserDisconnection(PList.GetPlayer(msg.PlayerCarac.ActorID).Carac, msg.Param1);
               PList.DelPlayer(msg.PlayerCarac.ActorID);
               break;

            case BBMsgType.ACTOR_SHOOT:
               Shooted(msg);
               break;

            case BBMsgType.ACTOR_SHOOT_RESPONSE:
               ShootResponsed(msg);
               break;

            case BBMsgType.KNOCK:
               if (msg.Param3 == OurID)
                  this.OnUserKnocked();
               OutputSystemText(PList.GetPlayer(msg.ActorInfo.Id).Carac.Nick + " knocked on " + PList.GetPlayer(msg.Param3).Carac.Nick + "'s door.", msg);
               break;
            case BBMsgType.COFFEE:
               if (msg.Param3 == OurID)
                  this.OnUserCoffee();
               //ShowInfoBalloon("Crying tears...", "Go Coffee...");
               OutputSystemText(PList.GetPlayer(msg.ActorInfo.Id).Carac.Nick + " invites " + PList.GetPlayer(msg.Param3).Carac.Nick + " to go to coffee", msg);
               break;
            case BBMsgType.EAT:
               if (msg.Param3 == OurID)
                  this.OnUserEat();
               //ShowInfoBalloon("Hungry", "Go to eat...");
               OutputSystemText(PList.GetPlayer(msg.ActorInfo.Id).Carac.Nick + " invites " + PList.GetPlayer(msg.Param3).Carac.Nick + " to go to eat", msg);
               break;
            case BBMsgType.JOIN_CONVERSATION:
               // Add the player to our player list 
               if (this.IsPartOfConversation(msg.Conversation))
               {
                  this.OnSendingSystemMessage("Player " + msg.PlayerCarac.Nick + " joined the conversation.", msg);
               }
               break;
            case BBMsgType.LEAVE_CONVERSATION:
               // Add the player to our player list  
               if (this.IsPartOfConversation(msg.Conversation))
               {
                  this.OnSendingSystemMessage(PList.GetPlayer(msg.PlayerCarac.ActorID).Carac.Nick + " leaved the conversation.", msg);
               }
               break;
            case BBMsgType.PRIVATE_MESSAGE:
               {
                  if (this.IsPartOfConversation(msg.Conversation))
                  {
                     string nick;
                     Color color;
                     if (msg.ActorInfo.Id != -1)
                     {
                        nick = PList.GetPlayer(msg.ActorInfo.Id).Carac.Nick;
                        color = PList.GetPlayer(msg.ActorInfo.Id).Carac.TextColor;
                     }
                     else
                     {
                        nick = "";
                        color = Color.Black; // Whatever
                     }

                     this.OnSendingMessage(msg);
                     //MainForm.AppendChatMsgBBMessage(msg, msg.Param1, nick, color, msg.Conversation.ConversID.ToString());

                     // Notify the user
                     OnNotifyMessage();
                  }
                  break;
               }
            case BBMsgType.PRIVATE_CONVERSATION:
               //ShowInfoBalloon("Conversation privée");
               break;
         }
      }

      private void MessageChat(BBMessage msg, bool NotifyMessage)
      {
         if (this.IsPartOfConversation(msg.Conversation))
         {
            string nick;
            Color color;
            if (msg.ActorInfo.Id != -1)
            {
               nick = PList.GetPlayer(msg.ActorInfo.Id).Carac.Nick;
               color = PList.GetPlayer(msg.ActorInfo.Id).Carac.TextColor;
            }
            else
            {
               nick = "";
               color = Color.Black; // Whatever
            }

            this.OnSendingMessage(msg);
            // Notify the user
            if (NotifyMessage)
               this.OnNotifyMessage();
         }
      }

      #endregion

      #region Tools

      /// <summary>
      /// Envoie un message d'administration
      /// </summary>
      /// <param name="message"></param>
      public void OutputSystemText(string message, BBMessage conversation)
      {
         this.OnSendingSystemMessage(message, conversation);
      }

      /// <summary>
      /// Vérifie que l'utilisateur fait partie de la conversation
      /// </summary>
      /// <param name="conversation"></param>
      /// <returns></returns>
      private bool IsPartOfConversation(BBConversation conversation)
      {
         if (conversation == null)
            return true;

         foreach (string play in conversation.Players)
         {
            if (PList.GetPlayer(play, OurID) != null)
            {
               if (OurID == PList.GetPlayer(play, OurID).Carac.ActorID)
                  return true;
            }
         }

         return false;
      }

      /// <summary>
      /// Obtient le joueur connecté
      /// </summary>
      /// <returns></returns>
      public Player GetActualPlayer()
      {
         return PList.GetPlayer(OurID);
      }

      /// <summary>
      /// Change la couleur d'écriture
      /// </summary>
      /// <param name="color"></param>
      public void ChangePlayerColor(Color color)
      {
         Player player = this.GetActualPlayer();
         if ((player != null) && (player.Carac != null))
         {
            player.Carac.TextColor = color;
         }
      }

      /// <summary>
      /// Change le nom de l'utilisateur
      /// </summary>
      /// <param name="nick"></param>
      public void ChangePlayerName(string nick)
      {
         //Player player = this.GetActualPlayer();
         //if ((player != null) && (player.Carac != null))
         //{
         //   player.Carac.Nick = nick;
         //}
      }

      #endregion

      private void ShootResponsed(BBMessage msg)
      {
         string dest = PList.GetPlayer(msg.ActorInfo.Id).Carac.Nick;
         string src = PList.GetPlayer(msg.Resp.SenderID).Carac.Nick;

         switch (msg.Resp.ShootResponse)
         {
            case ShootResponseDesc.Response.AVOIDED:
               OutputSystemText(dest + " avoided " + src + "'s snowball! He's too fast, is he really working ?", msg);
               break;

            case ShootResponseDesc.Response.COUNTERED:
               OutputSystemText(dest + " countered " + src + "'s snowball! Seems like he's working but still got some nerves", msg);
               break;

            case ShootResponseDesc.Response.TAKEN:
               OutputSystemText(dest + " couldn't handle " + src + "'s snowball... What a loser.", msg);
               break;

            case ShootResponseDesc.Response.TOO_MUCH:
               OutputSystemText(dest + " is already dealing with a snowball, " + src + " should calm down a bit.", msg);
               break;
         }
      }

      private void Shooted(BBMessage msg)
      {
         if (msg.Shoot.Cible_ID == OurID)
         {
            // We are the destination
            if (msg.ActorInfo.Id == OurID)
            {
               // Are you dumb ?
               OutputSystemText("You crush your snowball on your head. You feel cold, and somehow alone.", msg);
            }
            else
            {
               //MainForm.ShootSnowBall(PList.GetPlayer(msg.ActorInfo.Id).Carac.Nick);
            }
         }
         else
         {
            if (msg.ActorInfo.Id == msg.Shoot.Cible_ID)
               OutputSystemText(PList.GetPlayer(msg.ActorInfo.Id).Carac.Nick + " crushed a snowball on his own head, and looks dumb.", msg);
            else
               OutputSystemText(PList.GetPlayer(msg.ActorInfo.Id).Carac.Nick + " threw a snowball at " + PList.GetPlayer(msg.Shoot.Cible_ID).Carac.Nick + "'s head!", msg);
         }
      }

      public void Entered(string cmd)
      {
         if (Client != null)
         {
            if (Client.Status == NetConnectionStatus.Connected)
            {
               Player player = PList.GetPlayer(OurID);
               if (player != null)
               {
                  // Create a message
                  BBMessage msg = new BBMessage();
                  msg.MsgChat(cmd, player.Carac);

                  if (msg.PlayerCarac != null)
                     // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
                     Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
               }
            }
         }
      }

      /// <summary>
      /// Envoie un message pour le pendu
      /// </summary>
      /// <param name="cmd"></param>
      public void EnteredHelper(string cmd, BBMessage message)
      {
         if (Client != null)
         {
            if (Client.Status == NetConnectionStatus.Connected)
            {
               Player player = PList.GetPlayer(OurID);
               if (player != null)
               {
                  // Create a message
                  BBMessage msg = new BBMessage();
                  msg.MsgChatHelper(cmd, player.Carac, message);

                  if (msg.PlayerCarac != null)
                     // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
                     Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
               }
            }
         }
      }

      /// <summary>
      /// Envoie un message pour le pendu
      /// </summary>
      /// <param name="cmd"></param>
      public void EnteredPendu(string cmd, bool alert, BBMessage message)
      {
         if (Client != null)
         {
            if (Client.Status == NetConnectionStatus.Connected)
            {
               Player player = PList.GetPlayer(OurID);
               if (player != null)
               {
                  // Create a message
                  BBMessage msg = new BBMessage();
                  msg.MsgChatPendu(cmd, player.Carac, alert, message);

                  if (msg.PlayerCarac != null)
                     // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
                     Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
               }
            }
         }
      }

      /// <summary>
      /// Envoie un message pour VDM
      /// </summary>
      /// <param name="cmd"></param>
      public void EnteredVDM(BBMessage message, string cmd, bool displayMessage)
      {
         if (Client != null)
         {
            if (Client.Status == NetConnectionStatus.Connected)
            {
               Player player = PList.GetPlayer(OurID);
               if (player != null)
               {
                  // Create a message
                  BBMessage msg = new BBMessage();
                  msg.MsgChatVDM(cmd, player.Carac, displayMessage, message);

                  if (msg.PlayerCarac != null)
                     // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
                     Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
               }
            }
         }
      }

      /// <summary>
      /// Envoie un message pour BASH
      /// </summary>
      /// <param name="cmd"></param>
      public void EnteredBASH(string cmd, bool toDisplay, BBMessage message)
      {
         if (Client != null)
         {
            if (Client.Status == NetConnectionStatus.Connected)
            {
               Player player = PList.GetPlayer(OurID);
               if (player != null)
               {
                  // Create a message
                  BBMessage msg = new BBMessage();
                  msg.MsgChatBash(cmd, player.Carac, toDisplay, message);

                  if (msg.PlayerCarac != null)
                     // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
                     Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
               }
            }
         }
      }

      /// <summary>
      /// Envoie un tir
      /// </summary>
      /// <param name="nick"></param>
      public void Shoot(string nick)
      {
         // Create a message
         BBMessage msg = new BBMessage();
         msg.MsgActorShoot(new ShootDescription(ShootDescription.Type.SNOWBALL, PList.GetPlayer(nick, OurID).Carac.ActorID));

         // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
         Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
      }

      public void ShootResponse(ShootResponseDesc.Response response, string nick)
      {
         // Create a message
         int id = PList.GetPlayer(nick, OurID).Carac.ActorID;
         BBMessage msg = new BBMessage();
         msg.MsgShootResponse(new ShootResponseDesc(response, id));

         // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
         Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
      }

      public void Knock(string nick, BBConversation conversation)
      {
         // Create a message
         BBMessage msg = new BBMessage();
         msg.MsgKnock("", PList.GetPlayer(nick, OurID).Carac.ActorID);
         msg.Conversation = conversation;

         // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
         Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
      }

      public void Coffee(string nick, BBConversation conversation)
      {
         // Create a message
         BBMessage msg = new BBMessage();
         msg.MsgCoffee("", PList.GetPlayer(nick, OurID).Carac.ActorID);
         msg.Conversation = conversation;

         // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
         Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
      }

      public void Eat(string nick, BBConversation conversation)
      {
         // Create a message
         BBMessage msg = new BBMessage();
         msg.MsgEat("", PList.GetPlayer(nick, OurID).Carac.ActorID);
         msg.Conversation = conversation;

         // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
         Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
      }

      public void PrivateMessage(BBConversation convers, string text, string actualnick)
      {
         if (convers != null)
         {
            Player player = PList.GetPlayer(actualnick, OurID);
            if (player != null)
            {
               // Create a message
               BBMessage msg = new BBMessage();
               msg.Conversation = convers;
               msg.PlayerCarac = player.Carac;
               msg.MsgPrivateMessage(text, actualnick);
               msg.Message = text;

               if (msg.PlayerCarac != null)
                  // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
                  Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
            }
         }
      }

      public bool AskLogin(PlayerCarac playerCarac, string version)
      {
         // Create login message
         BBMessage msg = new BBMessage();
         msg.MsgAskLogin(playerCarac, version);

         // Send it.
         return Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
      }

      public bool MsgOKLogin(PlayerCarac playerCarac)
      {
         BBMessage msg = new BBMessage();
         playerCarac.AssignID();
         msg.PlayerCarac = playerCarac;
         msg.MsgType = BBMsgType.OK_LOGIN;

         return Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
      }

      public void LeaveConversation(BBConversation convers, string actualnick)
      {
         if (convers != null)
         {
            if (convers.Players.Contains(actualnick))
            {
               convers.Players.Remove(actualnick);

               if (PList.GetPlayer(actualnick, OurID) != null)
               {
                  BBMessage msg = new BBMessage();
                  msg.Conversation = convers;

                  msg.MsgLeaveConversationPlayer(PList.GetPlayer(actualnick, OurID).Carac.ActorID, string.Empty);

                  Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
               }
            }
         }
      }

      public void JoinConversation(BBConversation convers, string actualnick)
      {
         if (convers != null)
         {
            BBMessage msg = new BBMessage();
            msg.Conversation = convers;

            msg.MsgJoinConversationPlayer(PList.GetPlayer(actualnick, OurID), true);

            Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
         }
      }

      public BBConversation StartPrivateConversation(List<string> users)
      {
         BBConversation conversation = new BBConversation(users);

         //BBMessage msg = new BBMessage();
         //msg.Conversation = conversation;
         foreach (string str in users)
         {
            this.JoinConversation(conversation, str);
         }

         return conversation;
      }

      public void PrivateConversation(string nick, string text)
      {
         // Create a message
         BBMessage msg = new BBMessage();
         msg.MsgPrivateConversation(text, PList.GetPlayer(nick, OurID).Carac.ActorID);

         // Send use the ReliableUnordered channel; ie. it WILL arrive, but not necessarily in order
         Client.SendMessage(msg.GetNetMessage(), NetChannel.ReliableUnordered);
      }

      public static PlayerCarac CreatePlayerCarac(PlayerClass playerclass, string nick, Color color)
      {
         PlayerCarac Player = new PlayerCarac();
         Player.Nick = nick;
         Player.Type = playerclass;
         Player.TextColor = color;

         return Player;
      }

      public void SetConnectedStatus()
      {
         this.Client.SetConnectedStatus();
      }
   }
}
